"""
语法定义(EBNF格式):
{} - 表示出现0次或多次
[] - 表示可选，中括号里面的内容出现0次或1次
() - 表示分组
+ - 表示+号左边的项出现一次或多次
| - 表示逻辑或

program ::= {statement}
statement ::= "PRINT" (expression | string) nl
    | "IF" comparison "THEN" nl {statement} "ENDIF" nl
    | "WHILE" comparison "REPEAT" nl {statement} "ENDWHILE" nl
    | "LABEL" id nl
    | "GOTO" id nl
    | "LET" id "=" expression nl
    | "INPUT" id nl
comparison ::= expression (("==" | "!=" | ">" | ">=" | "<" | "<=") expression)+
expression ::= term {( "-" | "+" ) term}
term ::= unary {( "/" | "*" ) unary}
unary ::= ["+" | "-"] primary
primary ::= number | id
nl ::= '\n'+

"""
import sys
from lexer import TokenType, Lexer, Token


# 把每一条语法规则，映射乘Parser中的一个方法
class Parser:
    def __init__(self, lexer, emitter):
        self.lexer = lexer
        self.emitter = emitter

        self.cur_token = None
        self.peek_token = None
        # 调用两次初始化当前token和预读token
        self.next_token()
        self.next_token()

        # 未声明的变量或跳转到未定义的标签应当报错
        # PRINT index
        # GOTO main
        self.symbols = set()  # 声明的变量  LET和INPUT语句使用的变量必须先声明
        self.labels_declared = set()  # 声明的标签
        self.labels_gotoed = set() # 跳转的标签

    def check_token(self, kind):
        return kind == self.cur_token.kind

    def check_peek(self, kind):
        return kind == self.peek_token.kind

    # 匹配当前token,如果没有错误，移动到下一个token
    def match(self, kind):
        if not self.check_token(kind):
            self.abort(f'Expected {kind.name}, got {self.cur_token.kind.name}')
        self.next_token()

    def next_token(self):
        self.cur_token = self.peek_token
        self.peek_token = self.lexer.get_token()

    def abort(self, message):
        sys.exit('Error. ' + message)

    # program: := {statement}  程序是由0条或者多条语句构成
    def parse(self):
        self.emitter.header_line('#include <stdio.h>')
        self.emitter.header_line('int main(void) {')

        # 跳过源程序代码的起始换行符
        while self.check_token(TokenType.NEWLINE):
            self.next_token()

        while not self.check_token(TokenType.EOF):
            self.statement()

        self.emitter.emit_line('return 0;')
        self.emitter.emit_line('}')

        # 确保跳转的label已经定义过
        for label in self.labels_gotoed:
            if label not in self.labels_declared:
                self.abort(f'Attempting to GOTO an undeclared label: {label}')

    def statement(self):

        # "PRINT" (expression | string) nl
        if self.check_token(TokenType.PRINT):
            print('PRINT STATEMENT')
            self.next_token()  # 调用lexer中的get_token方法，会跳过空白字符

            if self.check_token(TokenType.STRING):  # 打印字符串
                self.emitter.emit_line(f'printf("{self.cur_token.value}\\n");')
                self.next_token()
            else:
                self.emitter.emit(f'printf("%2.f\\n", (float)(')
                self.expression()  # 表达式
                self.emitter.emit_line("));")

        # "IF" comparison "THEN" nl {statement} "ENDIF" nl
        elif self.check_token(TokenType.IF):
            self.next_token()
            self.emitter.emit('if (')
            self.comparison()

            self.match(TokenType.THEN)
            self.nl()
            self.emitter.emit_line(') {')

            # 在IF THEN ... ENDIF中间可能有很多语句，递归调用self.statement方法
            while not self.check_token(TokenType.ENDIF):
                self.statement()

            self.match(TokenType.ENDIF)
            self.emitter.emit_line('}')

        # "WHILE" comparison "REPEAT" nl {statement nl} "ENDWHILE" nl
        elif self.check_token(TokenType.WHILE):
            self.next_token()
            self.emitter.emit('while (')
            self.comparison()

            self.match(TokenType.REPEAT)
            self.nl()
            self.emitter.emit_line(') {')

            # 在WHILE  REPEAT ... ENDWHILE中可能有多条语句
            while not self.check_token(TokenType.ENDWHILE):
                self.statement()

            self.match(TokenType.ENDWHILE)
            self.emitter.emit_line('}')

        # "LABEL" id nl
        elif self.check_token(TokenType.LABEL):
            self.next_token()

            # label不能声明多次
            if self.cur_token.value in self.labels_declared:
                self.abort(f'Label already exists: {self.cur_token.value}')
            self.labels_declared.add(self.cur_token.value)

            self.emitter.emit_line(f'{self.cur_token.value}:')
            self.match(TokenType.ID)

        # "GOTO" id nl
        elif self.check_token(TokenType.GOTO):
            self.next_token()
            # 记录跳转的label
            self.labels_gotoed.add(self.cur_token.value)
            self.emitter.emit_line(f'goto {self.cur_token.value};')
            self.match(TokenType.ID)

        # "LET" id "=" expression nl
        elif self.check_token(TokenType.LET):
            self.next_token()

            # 把声明的变量名加入到self.symbols中
            if self.cur_token.value not in self.symbols:
                self.symbols.add(self.cur_token.value)
                self.emitter.header_line(f'float {self.cur_token.value};')

            self.emitter.emit(f'{self.cur_token.value} = ')
            self.match(TokenType.ID)
            self.match(TokenType.ASSIGN)
            self.expression()
            self.emitter.emit_line(';')

        # "INPUT" id nl
        elif self.check_token(TokenType.INPUT):
            self.next_token()

            # 把声明的变量名加入到self.symbols中
            if self.cur_token.value not in self.symbols:
                self.symbols.add(self.cur_token.value)
                self.emitter.header_line(f'float {self.cur_token.value};')

            self.emitter.emit_line(f'scanf("%f", &{self.cur_token.value});')
            self.match(TokenType.ID)

        else:
            self.abort(f'Invalid statement at {self.cur_token.value} ({self.cur_token.kind.name})')

        # 每条语句都有换行符
        self.nl()  # newline

    # comparison: := expression(("==" | "!=" | ">" | ">=" | "<" | "<=") expression)+
    def comparison(self):

        # 表达式 (比较运算符  表达式)+  +表示出现一次或者多次
        self.expression()

        # 至少出现一次 比较运算符 表达式
        if self.is_comparison_operator():
            self.emitter.emit(self.cur_token.value)
            self.next_token()
            self.expression()
        else:
            self.abort(f'Expected comparison operator at: {self.cur_token.value}')

        # 出现0次或多次 比较运算符 表达式
        while self.is_comparison_operator():
            self.emitter.emit(self.cur_token.value)
            self.next_token()
            self.expression()

    # 返回真，如果当前的token是比较运算符
    def is_comparison_operator(self):
        return any([
            self.check_token(TokenType.EQ),
            self.check_token(TokenType.NE),
            self.check_token(TokenType.GT),
            self.check_token(TokenType.GTEQ),
            self.check_token(TokenType.LT),
            self.check_token(TokenType.LTEQ),
        ])

    # An expression is something that can be evaluated to a value.
    # expression ::= term {( "-" | "+" ) term}
    def expression(self):
        self.term()

        # 0次或多次
        while self.check_token(TokenType.PLUS) or self.check_token(TokenType.MINUS):
            self.emitter.emit(self.cur_token.value)
            self.next_token()
            self.term()

    # term ::= unary {( "/" | "*" ) unary}
    def term(self):

        self.unary()
        while self.check_token(TokenType.SLASH) or self.check_token(TokenType.ASTERISK):
            self.emitter.emit(self.cur_token.value)
            self.next_token()
            self.unary()

    # unary ::= ["+" | "-"] primary
    def unary(self):
        if self.check_token(TokenType.PLUS) or self.check_token(TokenType.MINUS):
            self.emitter.emit(self.cur_token.value)
            self.next_token()
        self.primary()

    # primary ::= number | ident
    def primary(self):
        if self.check_token(TokenType.NUMBER):
            self.emitter.emit(self.cur_token.value)
            self.next_token()
        elif self.check_token(TokenType.ID):
            # 检查变量是否已经声明
            if self.cur_token.value not in self.symbols:
                self.abort(f'Referencing variable before assigment: {self.cur_token.value}')

            self.emitter.emit(self.cur_token.value)
            self.next_token()
        else:
            self.abort(f'Unexpected token at {self.cur_token.value}')

    # nl ::= '\n'+  允许多个换行符
    def nl(self):
        print('NEWLINE')

        self.match(TokenType.NEWLINE)

        while self.check_token(TokenType.NEWLINE):
            self.next_token()


# .tiny -> .c
# 生成C代码
class Emitter:
    def __init__(self, filepath):
        self.filepath = filepath
        self.header = ''
        self.code = ''

    def emit(self, code):
        self.code += code

    def emit_line(self, code):
        self.code += code + '\n'

    def header_line(self, code):
        self.header += code + '\n'

    def write_file(self):
        with open(self.filepath, 'w') as f:
            f.write(self.header + self.code)


if __name__ == '__main__':
    with open('./test.tiny') as f:
        src = f.read()

        lexer = Lexer(src)
        emitter = Emitter(filepath='./out.c')
        parser = Parser(lexer, emitter)
        parser.parse()
        emitter.write_file()
        print('编译完成！')